<!DOCTYPE HTML>
<html lang="zh-Hans" class="sidebar-visible no-js light">
    <head>
        <!-- Book generated using mdBook -->
        <meta charset="UTF-8">
        <title>译：Rust字符串格式化的幕后：format_args!() - Rust 优秀博文</title>


        <!-- Custom HTML head -->
        
        <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="theme-color" content="#ffffff" />

        <link rel="icon" href="../../favicon.svg">
        <link rel="shortcut icon" href="../../favicon.png">
        <link rel="stylesheet" href="../../css/variables.css">
        <link rel="stylesheet" href="../../css/general.css">
        <link rel="stylesheet" href="../../css/chrome.css">
        <link rel="stylesheet" href="../../css/print.css" media="print">

        <!-- Fonts -->
        <link rel="stylesheet" href="../../FontAwesome/css/font-awesome.css">
        <link rel="stylesheet" href="../../fonts/fonts.css">

        <!-- Highlight.js Stylesheets -->
        <link rel="stylesheet" href="../../highlight.css">
        <link rel="stylesheet" href="../../tomorrow-night.css">
        <link rel="stylesheet" href="../../ayu-highlight.css">

        <!-- Custom theme stylesheets -->

    </head>
    <body>
        <!-- Provide site root to javascript -->
        <script type="text/javascript">
            var path_to_root = "../../";
            var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
        </script>

        <!-- Work around some values being stored in localStorage wrapped in quotes -->
        <script type="text/javascript">
            try {
                var theme = localStorage.getItem('mdbook-theme');
                var sidebar = localStorage.getItem('mdbook-sidebar');

                if (theme.startsWith('"') && theme.endsWith('"')) {
                    localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
                }

                if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
                    localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
                }
            } catch (e) { }
        </script>

        <!-- Set the theme before any content is loaded, prevents flash -->
        <script type="text/javascript">
            var theme;
            try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
            if (theme === null || theme === undefined) { theme = default_theme; }
            var html = document.querySelector('html');
            html.classList.remove('no-js')
            html.classList.remove('light')
            html.classList.add(theme);
            html.classList.add('js');
        </script>

        <!-- Hide / unhide sidebar before it is displayed -->
        <script type="text/javascript">
            var html = document.querySelector('html');
            var sidebar = 'hidden';
            if (document.body.clientWidth >= 1080) {
                try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
                sidebar = sidebar || 'visible';
            }
            html.classList.remove('sidebar-visible');
            html.classList.add("sidebar-" + sidebar);
        </script>

        <nav id="sidebar" class="sidebar" aria-label="Table of contents">
            <div class="sidebar-scrollbox">
                <ol class="chapter"><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">1.</strong> 类型系统</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../01_类型系统/Rust_Concept_Clarification_Deref_vs_AsRef_vs_Borrow_vs_Cow/Rust_Concept_Clarification_Deref_vs_AsRef_vs_Borrow_vs_Cow.html"><strong aria-hidden="true">1.1.</strong> Rust Concept Clarification Deref vs AsRef vs Borrow vs Cow</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Rust_Concept_Clarification_Deref_vs_AsRef_vs_Borrow_vs_Cow/Deref_AsRef_Borrow_Cow释义.html"><strong aria-hidden="true">1.2.</strong> Deref AsRef Borrow Cow 释义</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Rust的Borrow和AsRef：让你的代码用起来像呼吸一样自然/Rust的Borrow和AsRef：让你的代码用起来像呼吸一样自然.html"><strong aria-hidden="true">1.3.</strong> Rust的Borrow和AsRef：让你的代码用起来像呼吸一样自然</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Rust的Cow类型有什么用？详解Cow及其用途/Rust的Cow类型有什么用？详解Cow及其用途.html"><strong aria-hidden="true">1.4.</strong> Rust的Cow类型有什么用？详解Cow及其用途</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/判别Fn、FnMut、FnOnce的标准/判别Fn、FnMut、FnOnce的标准.html"><strong aria-hidden="true">1.5.</strong> 判别Fn、FnMut、FnOnce的标准</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/一行代码告诉你内部可变性的真相(UnsafeCell)/一行代码告诉你内部可变性的真相(UnsafeCell).html"><strong aria-hidden="true">1.6.</strong> 一行代码告诉你内部可变性的真相(UnsafeCell)</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Tour_of_Rust's_Standard_Library_Traits/Tour_of_Rust's_Standard_Library_Traits.html"><strong aria-hidden="true">1.7.</strong> Tour of Rust's Standard Library Traits</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/逆变、协变与子类型，以及Rust/逆变、协变与子类型，以及Rust.html"><strong aria-hidden="true">1.8.</strong> 逆变、协变与子类型，以及Rust</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Rust自引用结构、Pin与Unpin/Rust自引用结构、Pin与Unpin.html"><strong aria-hidden="true">1.9.</strong> Rust自引用结构、Pin与Unpin</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/为什么Rust需要Pin,Unpin/为什么Rust需要Pin,Unpin.html"><strong aria-hidden="true">1.10.</strong> 译：为什么 Rust 需要 Pin, Unpin ？</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/定海神针Pin和Unpin/定海神针Pin和Unpin.html"><strong aria-hidden="true">1.11.</strong> 译：定海神针 Pin 和 Unpin</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/sizedness-in-rust/sizedness-in-rust.html"><strong aria-hidden="true">1.12.</strong> Sizedness in Rust</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Rust生命周期集大成者PhantomData〈T〉/Rust生命周期集大成者PhantomData〈T〉.html"><strong aria-hidden="true">1.13.</strong> Rust生命周期集大成者 PhantomData&lt;T&gt;</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：用Rust做类型体操_Part_0.html"><strong aria-hidden="true">1.14.</strong> 数据库表达式执行的黑魔法：用Rust做类型体操 Part 0</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：GAT实现引用类型关联_Part_1.html"><strong aria-hidden="true">1.15.</strong> 数据库表达式执行的黑魔法：GAT实现引用类型关联 Part 1</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：用HRTB写bound_Part_2.html"><strong aria-hidden="true">1.16.</strong> 数据库表达式执行的黑魔法：用HRTB写bound Part 2</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：用Rust做类型体操之用宏展开重复代码_Part_3_&_4.html"><strong aria-hidden="true">1.17.</strong> 数据库表达式执行的黑魔法：用Rust做类型体操之用宏展开重复代码 Part 3 & 4</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：与Rust编译器斗智斗勇之表达式向量化_Part_5_&_6.html"><strong aria-hidden="true">1.18.</strong> 数据库表达式执行的黑魔法：与Rust编译器斗智斗勇之表达式向量化 Part 5 & 6</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：在Rust中用宏关联逻辑类型和实际类型_Part_7.html"><strong aria-hidden="true">1.19.</strong> 数据库表达式执行的黑魔法：在Rust中用宏关联逻辑类型和实际类型 Part 7</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">2.</strong> 生命周期</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../02_生命周期/Rust中的生命周期——从StrSplit实例说开去/Rust中的生命周期——从StrSplit实例说开去.html"><strong aria-hidden="true">2.1.</strong> Rust中的生命周期——从StrSplit实例说开去</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/与ChatGPT深度对话来学Rust生命周期/与ChatGPT深度对话来学Rust生命周期.html"><strong aria-hidden="true">2.2.</strong> 与ChatGPT深度对话来学Rust生命周期</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/进击的Rust生命周期——early_bound与late_bound（1）/进击的Rust生命周期——early_bound与late_bound（1）.html"><strong aria-hidden="true">2.3.</strong> 进击的Rust生命周期——early_bound与late_bound（1）</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/进击的Rust生命周期——early_bound与late_bound（2）/进击的Rust生命周期——early_bound与late_bound（2）.html"><strong aria-hidden="true">2.4.</strong> 进击的Rust生命周期——early_bound与late_bound（2）</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/进击的Rust生命周期——一力降十会的MIR（1）/进击的Rust生命周期——一力降十会的MIR（1）.html"><strong aria-hidden="true">2.5.</strong> 进击的Rust生命周期——一力降十会的MIR（1）</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/进击的Rust生命周期——一力降十会的MIR（2）/进击的Rust生命周期——一力降十会的MIR（2）.html"><strong aria-hidden="true">2.6.</strong> 进击的Rust生命周期——一力降十会的MIR（2）</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/Common_Rust_Lifetime_Misconceptions/Common_Rust_Lifetime_Misconceptions.html"><strong aria-hidden="true">2.7.</strong> Common Rust Lifetime Misconceptions</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/Rust生命周期常见误区/Rust生命周期常见误区.html"><strong aria-hidden="true">2.8.</strong> 译：Rust生命周期常见误区</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">3.</strong> 无畏并发</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../05_无畏并发/简单写个Rust无锁队列/简单写个Rust无锁队列.html"><strong aria-hidden="true">3.1.</strong> 简单写个Rust无锁队列</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/进击的Rust多线程——混合自旋锁/进击的Rust多线程——混合自旋锁.html"><strong aria-hidden="true">3.2.</strong> 进击的Rust多线程——混合自旋锁</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/An_unsafe_tour_of_Rust's_Send_and_Sync/An_unsafe_tour_of_Rust's_Send_and_Sync.html"><strong aria-hidden="true">3.3.</strong> An unsafe tour of Rust's Send and Sync</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/进击的Rust多线程——Send与Sync/进击的Rust多线程——Send与Sync.html"><strong aria-hidden="true">3.4.</strong> 进击的Rust多线程——Send与Sync</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/进击的Rust多线程——离经叛道的PhantomData/进击的Rust多线程——离经叛道的PhantomData.html"><strong aria-hidden="true">3.5.</strong> 进击的Rust多线程——离经叛道的PhantomData</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/Rust_Async_Pin概念解析/Rust_Async_Pin概念解析.html"><strong aria-hidden="true">3.6.</strong> Rust Async: Pin概念解析</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/Rust和C++的并发库对比/Rust和C++的并发库对比.html"><strong aria-hidden="true">3.7.</strong> 译：Rust 和 C++ 的并发库对比</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/Rust原子类型和内存排序/Rust原子类型和内存排序.html"><strong aria-hidden="true">3.8.</strong> Rust原子类型和内存排序</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">4.</strong> 网络编程</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../06_网络编程/从编解码层面理解WebSocket_手写一个WebSocket/从编解码层面理解WebSocket_手写一个WebSocket.html"><strong aria-hidden="true">4.1.</strong> 从编解码层面理解WebSocket 手写一 个WebSocket</a></li><li class="chapter-item expanded "><a href="../../06_网络编程/透过Rust探索系统的本原：网络篇/透过Rust探索系统的本原：网络篇.html"><strong aria-hidden="true">4.2.</strong> 透过Rust探索系统的本原：网络篇</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">5.</strong> 轮子系列</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../07_轮子系列/700行Rust写一个内存分配器/700行Rust写一个内存分配器.html"><strong aria-hidden="true">5.1.</strong> 700行Rust写一个内存分配器</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/Rust：网络库的实现思路/Rust：网络库的实现思路.html"><strong aria-hidden="true">5.2.</strong> Rust：网络库的实现思路</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/Rust异步运行时基础部件/Rust异步运行时基础部件.html"><strong aria-hidden="true">5.3.</strong> Rust异步运行时基础部件</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/使用Rust+epoll编写异步IO框架/使用Rust+epoll编写异步IO框架（1）.html"><strong aria-hidden="true">5.4.</strong> 使用Rust+epoll编写异步IO框架（1）</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/使用Rust+epoll编写异步IO框架/使用Rust+epoll编写异步IO框架（2）.html"><strong aria-hidden="true">5.5.</strong> 使用Rust+epoll编写异步IO框架（2）</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/使用Rust+epoll编写异步IO框架/使用Rust+epoll编写异步IO框架（3）.html"><strong aria-hidden="true">5.6.</strong> 使用Rust+epoll编写异步IO框架（3）</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/用rust从零开发一套web框架/用rust从零开发一套web框架：day1.html"><strong aria-hidden="true">5.7.</strong> 用rust从零开发一套web框架：day1</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/用rust从零开发一套web框架/用rust从零开发一套web框架：day2.html"><strong aria-hidden="true">5.8.</strong> 用rust从零开发一套web框架：day2</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/用rust从零开发一套web框架/用rust从零开发一套web框架：day3.html"><strong aria-hidden="true">5.9.</strong> 用rust从零开发一套web框架：day3</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/用rust从零开发一套web框架/用rust从零开发一套web框架：day4.html"><strong aria-hidden="true">5.10.</strong> 用rust从零开发一套web框架：day4</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/用rust从零开发一套web框架/用rust从零开发一套web框架：day5.html"><strong aria-hidden="true">5.11.</strong> 用rust从零开发一套web框架：day5</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">6.</strong> 奇技淫巧</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../08_奇技淫巧/Copy-On-Write是不是优化？/Copy-On-Write是不是优化？.html"><strong aria-hidden="true">6.1.</strong> 译：Copy-On-Write是不是优化？</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/揭秘神奇的Rust_Axum风格的函数实现/揭秘神奇的Rust_Axum风格的函数实现.html"><strong aria-hidden="true">6.2.</strong> 译：揭秘神奇的 Rust Axum 风格的函数实现</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/“变长参数”函数与回调/“变长参数”函数与回调.html"><strong aria-hidden="true">6.3.</strong> “变长参数”函数与回调</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/Rust字符串格式化的幕后：format_args!()/Rust字符串格式化的幕后：format_args!().html" class="active"><strong aria-hidden="true">6.4.</strong> 译：Rust字符串格式化的幕后：format_args!()</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/给Rust带来一点C++特产/给Rust带来一点C++特产.html"><strong aria-hidden="true">6.5.</strong> 给Rust带来一点C++特产</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/一步步实现_Rust_Bevy_ECS_的_System_简化版本/一步步实现_Rust_Bevy_ECS_的_System_简化版本.html"><strong aria-hidden="true">6.6.</strong> 一步步实现 Rust Bevy ECS 的 System 简化版本</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/Exploring_Design_Patterns_in_Rust_with_Algorithmic_Trading_Examples/Exploring_Design_Patterns_in_Rust_with_Algorithmic_Trading_Examples.html"><strong aria-hidden="true">6.7.</strong> Exploring Design Patterns in Rust with Algorithmic Trading Examples</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">7.</strong> 源码分析</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../09_源码分析/Rust并发：bytes源码分析/Rust并发：bytes源码分析.html"><strong aria-hidden="true">7.1.</strong> Rust并发：bytes源码分析</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust并发：标准库Arc源码分析/Rust并发：标准库Arc源码分析.html"><strong aria-hidden="true">7.2.</strong> Rust并发：标准库Arc源码分析</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust并发：标准库sync_Once源码分析/Rust并发：标准库sync_Once源码分析.html"><strong aria-hidden="true">7.3.</strong> Rust并发：标准库sync::Once源码分析</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust源码阅读：引用计数Rc/Rust源码阅读：引用计数Rc.html"><strong aria-hidden="true">7.4.</strong> Rust源码阅读：引用计数Rc</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust源码阅读：Cell、RefCell与内部可变性/Rust源码阅读：Cell、RefCell与内部可变性.html"><strong aria-hidden="true">7.5.</strong> Rust源码阅读： Cell、RefCell与内部可变性</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/关于_Rust_的_UnsafeCell、Cell_与_RefCell/关于_Rust_的_UnsafeCell、Cell_与_RefCell.html"><strong aria-hidden="true">7.6.</strong> 关于 Rust 的 UnsafeCell、Cell 与 RefCell</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust_Async_async-stream源码分析/Rust_Async_async-stream源码分析.html"><strong aria-hidden="true">7.7.</strong> Rust Async: async-stream源码分析</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/走进Tokio的异步世界/走进Tokio的异步世界.html"><strong aria-hidden="true">7.8.</strong> 走进 Tokio 的异步世界</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/tokio.rs_runtime的实现/tokio.rs_runtime的实现.html"><strong aria-hidden="true">7.9.</strong> tokio.rs runtime 的实现</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Tokio_internals/Tokio_internals.html"><strong aria-hidden="true">7.10.</strong> Tokio internals</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Tokio_internals/译文：Tokio內部机制.html"><strong aria-hidden="true">7.11.</strong> 译：Tokio 内部机制</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust_Axum_HTTP_框架的架构分析/Rust_Axum_HTTP_框架的架构分析.html"><strong aria-hidden="true">7.12.</strong> Rust Axum HTTP 框架的架构分析</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/安利一个Rust_Game_Engine：Bevy--ECS部分/安利一个Rust_Game_Engine：Bevy--ECS部分.html"><strong aria-hidden="true">7.13.</strong> 安利一个Rust Game Engine：Bevy--ECS部分</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Tokio_解析之任务调度/Tokio_解析之任务调度.html"><strong aria-hidden="true">7.14.</strong> Tokio 解析之任务调度</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">8.</strong> 生态观察</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../11_生态观察/Rust_web_frameworks_have_subpar_error_reporting/Rust_web_frameworks_have_subpar_error_reporting.html"><strong aria-hidden="true">8.1.</strong> Rust web frameworks have subpar error reporting</a></li><li class="chapter-item expanded "><a href="../../11_生态观察/SeaORM：要做Rust版本的ActiveRecord/SeaORM：要做Rust版本的ActiveRecord.html"><strong aria-hidden="true">8.2.</strong> SeaORM：要做Rust版本的ActiveRecord</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">9.</strong> 死灵终极</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_总述.html"><strong aria-hidden="true">9.1.</strong> 译：Learn Rust the Dangerous Way 总述</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_0.html"><strong aria-hidden="true">9.2.</strong> 译：Learn Rust the Dangerous Way 0</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_1.html"><strong aria-hidden="true">9.3.</strong> 译：Learn Rust the Dangerous Way 1</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_2.html"><strong aria-hidden="true">9.4.</strong> 译：Learn Rust the Dangerous Way 2</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_3.html"><strong aria-hidden="true">9.5.</strong> 译：Learn Rust the Dangerous Way 3</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_4.html"><strong aria-hidden="true">9.6.</strong> 译：Learn Rust the Dangerous Way 4</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_5.html"><strong aria-hidden="true">9.7.</strong> 译：Learn Rust the Dangerous Way 5</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Unsafe_Rust_随堂小测/Unsafe_Rust_随堂小测（一）.html"><strong aria-hidden="true">9.8.</strong> Unsafe Rust 随堂小测（一）</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Unsafe_Rust_随堂小测/Unsafe_Rust_随堂小测（二）.html"><strong aria-hidden="true">9.9.</strong> Unsafe Rust 随堂小测（二）</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Unsafe_Rust_随堂小测/Unsafe_Rust_随堂小测（三）.html"><strong aria-hidden="true">9.10.</strong> Unsafe Rust 随堂小测（三）</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Unsafe_Rust_随堂小测/Unsafe_Rust_随堂小测参考答案.html"><strong aria-hidden="true">9.11.</strong> Unsafe Rust 随堂小测参考答案</a></li></ol></li></ol>
            </div>
            <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
        </nav>

        <div id="page-wrapper" class="page-wrapper">

            <div class="page">
                                <div id="menu-bar-hover-placeholder"></div>
                <div id="menu-bar" class="menu-bar sticky bordered">
                    <div class="left-buttons">
                        <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
                            <i class="fa fa-bars"></i>
                        </button>
                        <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
                            <i class="fa fa-paint-brush"></i>
                        </button>
                        <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
                            <li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
                        </ul>
                        <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
                            <i class="fa fa-search"></i>
                        </button>
                    </div>

                    <h1 class="menu-title">Rust 优秀博文</h1>

                    <div class="right-buttons">
                        <a href="../../print.html" title="Print this book" aria-label="Print this book">
                            <i id="print-button" class="fa fa-print"></i>
                        </a>

                    </div>
                </div>

                <div id="search-wrapper" class="hidden">
                    <form id="searchbar-outer" class="searchbar-outer">
                        <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
                    </form>
                    <div id="searchresults-outer" class="searchresults-outer hidden">
                        <div id="searchresults-header" class="searchresults-header"></div>
                        <ul id="searchresults">
                        </ul>
                    </div>
                </div>

                <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
                <script type="text/javascript">
                    document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
                    document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
                    Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
                        link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
                    });
                </script>

                <div id="content" class="content">
                    <main>
                        <h1 id="rust字符串格式化的幕后format_args"><a class="header" href="#rust字符串格式化的幕后format_args">Rust字符串格式化的幕后：format_args!()</a></h1>
<p>原载：<a href="https://blog.m-ou.se/format-args">https://blog.m-ou.se/format-args</a></p>
<p>作者：Mara</p>
<p>译文：<a href="https://zhuanlan.zhihu.com/p/693641844">https://zhuanlan.zhihu.com/p/693641844</a></p>
<p>译者：<a href="https://www.zhihu.com/people/cinea4678">Cinea4678</a></p>
<blockquote>
<p>本文原作者：<a href="https://link.zhihu.com/?target=https%3A//blog.m-ou.se/format-args/">Mara Bos</a>，原文链接：<a href="https://link.zhihu.com/?target=https%3A//blog.m-ou.se/format-args/">https://blog.m-ou.se/format-args/</a></p>
</blockquote>
<p><a href="https://link.zhihu.com/?target=https%3A//doc.rust-lang.org/stable/std/fmt/struct.Arguments.html">fmt::Arguments</a>是Rust标准库中我最喜欢的类型之一。虽然它并不算特别惊艳，但它却是一个几乎所有Rust程序都在使用的优秀构件。这个类型，连同<a href="https://link.zhihu.com/?target=https%3A//doc.rust-lang.org/stable/std/macro.format_args.html">format_args!()宏</a>一起，在背后支撑了<code>print!()</code>、<code>format!()</code>、<code>log::info!()</code>和其他众多的文本格式化宏。这些宏既有来自于标准库的，也有来自于社区包的。</p>
<p>在这篇博客里，我们会学习它是怎样工作的、它现在是如何被实现的，以及它未来可能会如何改变。</p>
<blockquote>
<p>译注：原文发布于2023年12月5日。</p>
</blockquote>
<h2 id="format_args"><a class="header" href="#format_args"><code>format_args!()</code></a></h2>
<p>当你写下类似这样的代码时：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>print!(&quot;Hello {}!\n&quot;, name);
<span class="boring">}
</span></code></pre></pre>
<p><code>print</code>宏稍后会展开为这样的代码：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>std::io::_print(format_args!(&quot;Hello, {}!\n&quot;, name));
<span class="boring">}
</span></code></pre></pre>
<p><code>_print</code>是一个将<code>fmt::Arguments</code>作为唯一参数的内部函数。<code>fmt::Arguments</code>则由内建的<code>format_args!()</code>宏产生，这是一个能够读懂<a href="https://link.zhihu.com/?target=https%3A//doc.rust-lang.org/stable/std/fmt/index.html">Rust的字符串格式化语法</a>（使用<code>{}</code>作为占位符等）的宏。生成的<code>fmt::Arguments</code>对象既代表<em>字符串模板</em>，即带有占位符的（解析后的）格式字符串（在本例中为：<code>&quot;Hello, &lt;argument 1 here&gt;\n&quot;</code>），又代表对参数的引用（在本例中仅有一个：<code>&amp;name</code>）。</p>
<p>因为它是一个宏，所以对格式化字符串的解析<em>在编译期</em>就已经完成了。（这里和C中的<code>printf</code>等函数不一样，它们是在运行时解析和处理<code>%</code>占位符的。）</p>
<p>这意味着<code>format_args!()</code>宏可以给出编译错误，例如占位符和参数不匹配。</p>
<p>这也意味着它可以将字符串模板转换成一种更易于在运行时处理的表现形式。例如：<code>[Str(&quot;Hello, &quot;), Arg(0), Str(&quot;!\n&quot;)]</code>。在我们的<code>print</code>例子中，展开<code>format_args</code>会得到类似这样的结果：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>std::io::_print(
    // 简化版的 format_args!() 展开:
    std::fmt::Arguments {
        template: &amp;[Str(&quot;Hello, &quot;), Arg(0), Str(&quot;!\n&quot;)],
        arguments: &amp;[&amp;name as &amp;dyn Display],
    }
);
<span class="boring">}
</span></code></pre></pre>
<p>当涉及不同格式化特性（例如<code>Display</code>、<code>Debug</code>、<code>LowerHex</code>等）或标志（例如<code>{:02x}</code>、<code>{:.9}</code>、<code>{:#?}</code>等）的混合 时，情况会变得更复杂一些，但总体思路是不变的。</p>
<p><code>_print</code>函数对<code>fmt::Arguments</code>类型的了解并不多。它仅仅包含一个<code>fn write_str(&amp;mut self, &amp;str)</code>的实现，以将 一个<code>&amp;str</code>写到标准输出。并且<a href="https://link.zhihu.com/?target=https%3A//doc.rust-lang.org/stable/std/fmt/trait.Write.html">fmt::Write特性</a>会方便地添加一个<code>fn write_fmt(&amp;mut self, fmt::Arguments)</code>方法。使用<code>fmt::Arguments</code>对象调用这个<code>write_fmt</code>方法将会产生一系列对<code>write_str</code>的调用以产生格式化的输出。</p>
<p>提供的<code>write_fmt</code>方法只是简单地调用<code>std::fmt::write()</code>，这是唯一一个知道怎么“执行”<code>fmt::Arguments</code>类型中的格式化指令的函数。它为模板中的静态部分调用<code>write_str</code>，为参数调用正确的<code>Display::fmt</code>（或<code>LowerHex::fmt</code>等）函数（也会产生对<code>write_str</code>的调用）。</p>
<h2 id="使用范例"><a class="header" href="#使用范例">使用范例</a></h2>
<p>我们刚刚了解到，要想利用Rust字符串格式化的强大功能，只需要为你的类型提供<code>Write::write_str</code>实现即可。</p>
<p>例如：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct Terminal;

impl std::fmt::Write for Terminal {
    fn write_str(&amp;mut self, s: &amp;str) -&gt; std::fmt::Result {
        write_to_terminal(s.as_bytes());
        Ok(())
    }
}
<span class="boring">}
</span></code></pre></pre>
<p>这就是为了让它工作所需要做的一切。接下来请看：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>Terminal.write_fmt(format_args!(&quot;Hello, {name}!\n&quot;));
<span class="boring">}
</span></code></pre></pre>
<p>这会产生对你的<code>write_str</code>函数的一系列调用：<code>write_str(&quot;Hello, &quot;)</code>、<code>write_str(name)</code>和<code>write_str(&quot;!\n&quot;)</code>。</p>
<p>并且，感谢<a href="https://link.zhihu.com/?target=https%3A//doc.rust-lang.org/stable/std/macro.write.html">write宏</a>，上面那行代码可以被方便地写作：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>write!(Terminal, &quot;Hello, {name}!\n&quot;);
<span class="boring">}
</span></code></pre></pre>
<p>换句话说，你甚至不需要知道<code>fmt::Arguments</code>或着<code>format_args!()</code>的存在，甚至不需要为自己的类型添加格式化功能：只需要实现<code>Write::write_str</code>，<code>write!()</code>宏就能正常工作！</p>
<h2 id="实现细节"><a class="header" href="#实现细节">实现细节</a></h2>
<p>让我感到兴奋的是，<code>fmt::Arguments</code>的实现细节是完全私有的。作为Rust标准库的开发者，我们可以改变<code>fmt::Arguments</code>内部所有表示数据的方式，只要我们相应地更新<code>format_args!()</code>和<code>std::fmt::Write</code>的实现。所有正在使用格式化的代码，从<code>dbg!(x)</code>到<code>log::info!(n = {n}&quot;)</code>，以及我们上文中的例子，甚至都不会注意到它们依赖的构建组件发生过变化，并与此同时从潜在的优化中受益。</p>
<p>所以困扰我多年的问题现在变成了：什么才是<code>fmt::Arguments</code>的最高效、最轻便、性能最好、编译最快、总体来说最好的实现？</p>
<p>我还不知道答案。</p>
<p>我认为没有唯一的答案，因为其中涉及很多权衡。我可以肯定的是，我们<em>目前</em>的实现并不是这种实现。它还不错，但还有很多地方可以改进。</p>
<h2 id="当前的实现"><a class="header" href="#当前的实现">当前的实现</a></h2>
<p>今天的实现看起来像这样：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct Arguments&lt;'a&gt; {
    pieces: &amp;'a [&amp;'static str],
    placeholders: Option&lt;&amp;'a [Placeholder]&gt;,
    args: &amp;'a [Argument&lt;'a&gt;],
}
<span class="boring">}
</span></code></pre></pre>
<p><code>piece</code>字段包含来自模板的字面字符串。举个例子，对<code>format_args!(&quot;a{}b{}c&quot;)</code>，这个字段是<code>&amp;[&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]</code>。</p>
<p>这些字符串之间的占位符（<code>{}</code>）被列举在<code>placeholder</code>字段中，它包含了格式化的选项及将被格式化的参数：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct Placeholder {
    argument: usize, // args 字段中的索引
    fill: char,
    align: Alignment,
    flags: u32,
    precision: Count,
    width: Count,
}
<span class="boring">}
</span></code></pre></pre>
<p>在一个例如<code>format_args!(&quot;a{1:&gt;012}b{1:-5}c{0:#.1}&quot;)</code>的复杂例子中，所有这些字段都非常重要。</p>
<p>不过，在很多情况下，就像<code>format_args!(&quot;a{}b{}c{}&quot;)</code>，占位符仅仅只是参数的有序排列，所有标志和设置都是默认的。这就是为什么<code>placeholders</code>字段是个<code>Option</code>：一般情况下可以设为<code>None</code>，节省一些存储空间。</p>
<p>最后，<code>args</code>字段包含了将被格式化的参数。这些参数其实可以直接存储为<code>&amp;dyn Display</code>，不这样做是因为我们还需要支持<code>Debug</code>、<code>LowerHex</code>和其它的显示类特性。</p>
<p>所以，相反地，我们使用了一个自定义的<code>Argument</code>类型，其行为和<code>&amp;dyn Display</code>几乎完全一致。它以两个指针的形式实现：一个指向这个参数本身，另一个指向了对应的<code>Display::fmt</code>（或着<code>Debug::fmt</code>等）实现。</p>
<p>这意味着当一个参数被通过两个不同的特性使用时，它被存储到了<code>args</code>两次。举个例子，<code>format_args!(&quot;{0} {0:?} {1:x}&quot;, a, b)</code>的结果是：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>fmt::Arguments {
    pieces: &amp;[&quot;&quot;, &quot; &quot;, &quot; &quot;],
    placeholders: None,
    args: &amp;[
        fmt::Argument::new(&amp;a, Display::fmt),
        fmt::Argument::new(&amp;a, Debug::fmt),
        fmt::Argument::new(&amp;b, LowerHex::fmt),
    ],
}
<span class="boring">}
</span></code></pre></pre>
<p>不过，当一个参数被以相同的<em>特性</em>但不同的<em>标志</em>使用了两次时，它只会在<code>args</code>中出现一次。举个例子，<code>format_args!(&quot;{0:?} {0:#?}&quot;, a)</code>，其中<code>a</code>以<code>Debug</code>分别格式化了两次，一次打开了美化输出，另一次没有。展开的结果是：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>fmt::Arguments {
    pieces: &amp;[&quot;&quot;, &quot; &quot;],
    placeholders: Some(&amp;[
        fmt::Placeholder { argument: 0, ..default() },
        fmt::Placeholder { argument: 0, flags: 4 /* alternate */, ..default() },
    ]),
    args: &amp;[
        fmt::Argument::new(&amp;a, Debug::fmt),
    ],
}
<span class="boring">}
</span></code></pre></pre>
<p><code>fmt::Arguments</code>类型被设计为使尽可能多的数据能够<em>提升为常量</em>。<code>pieces</code>和<code>placeholders</code>字段仅引用可以放置在静态位置的常量数据，因此实际上前两个<code>&amp;[]</code>就是<code>&amp;'static []</code>。只有被有意保持为尽可能小的<code>args</code>字段，它的数组需要在运行时构建。这是唯一一个包含非静态数据，也就是对参数本身的引用，的数组。</p>
<p>尽管当前的设计有一些可圈可点的地方，<code>fmt::Arguments</code>如今的实现仍然有一些问题，或着至少是可供优化的机会。让我们聊聊这些问题中最有意思的那几个吧。</p>
<h3 id="结构体体积"><a class="header" href="#结构体体积">结构体体积</a></h3>
<p>首先，<code>format_args!(&quot;a{}b{}c{}d&quot;)</code>扩展为了一个包含<code>&amp;[&quot;a&quot;, &quot;b&quot;, &quot;c&quot;, &quot;d&quot;]</code>的结构体。这意味着这4个字节现在需要10个额外指针的空间：在64位的系统上，就是80个字节！（每个<code>&amp;str</code>需要存储一个指针和一个长度，并且外层的<code>&amp;[]</code>也被存储为一个指针和一个长度。）正如你能想象的，许多现实世界中对格式化宏的使用最终都归于小字符串碎片，像<code>&quot; &quot;</code>和<code>&quot;\n&quot;</code>，其中每个小字符串都会占据16字节的额外空间，而这仅仅只是为了1字节的数据！</p>
<p>在那之上，如果我们添加了任意一个标志或着其它格式化选项到任意一个占位符上，哪怕仅仅只是一个，<code>placeholders</code>字段就会从<code>None</code>切换为包含<em>所有</em>占位符信息的<code>Some(&amp;[...])</code>。举个例子，<code>format_args!(&quot;{a}{b}{c}{d:#}{e}{f}{g}&quot;)</code>会扩展为：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>fmt::Arguments {
    pieces: &amp;[&quot;&quot;, &quot; &quot;],
    placeholders: Some(&amp;[
        fmt::Placeholder { argument: 0, ..default() },
        fmt::Placeholder { argument: 1, ..default() },
        fmt::Placeholder { argument: 2, ..default() },
        fmt::Placeholder { argument: 3, flags: 4 /* alternate */, ..default() },
        fmt::Placeholder { argument: 4, ..default() },
        fmt::Placeholder { argument: 5, ..default() },
        fmt::Placeholder { argument: 6, ..default() },
    ]),
    args: &amp;[
        fmt::Argument::new(&amp;a, Debug::fmt),
        fmt::Argument::new(&amp;b, Debug::fmt),
        fmt::Argument::new(&amp;c, Debug::fmt),
        fmt::Argument::new(&amp;d, Debug::fmt),
        fmt::Argument::new(&amp;e, Debug::fmt),
        fmt::Argument::new(&amp;f, Debug::fmt),
        fmt::Argument::new(&amp;g, Debug::fmt),
    ],
}
<span class="boring">}
</span></code></pre></pre>
<p>如果第4个占位符没有<code>#</code>标志的话，<code>placeholders</code>就会变成<code>None</code>。这意味着一个标志的存储开销是一个占位符结构的7倍，在64位平台上，总计将近400字节！</p>
<p>哪怕我们不关心静态存储的大小，<code>fmt::Arguments</code>对象本身也是比实际的需要更大一些的。它包含了三个引用切片，指针加长度的大小乘以三，在64位平台上就是48字节的开销。如果<code>fmt::Arguments</code>的大小只有一个或两个指针，那么传递它的效率就会高得多。</p>
<h3 id="代码体积"><a class="header" href="#代码体积">代码体积</a></h3>
<p>如果你在意（静态）存储的大小，那你肯定也会关心代码的大小。</p>
<p>设计显示特性的一个问题，就是一个单一的特性实现被用于许多不同的标志和选项。也就是说，虽然<code>Display for i32</code>不必支持十六进制格式化（留给<code>LowerHex for i32</code>来实现），它却必须支持诸如对齐、填充字符、正负号、填充0之类的选项。</p>
<p>所以，一个简单的<code>println!(&quot;{}&quot;, some_integer)</code>会创建一个<code>fmt::Arguments</code>，它含有一个指向<code>&lt;i32 as Display&gt;::fmt</code>函数的指针，而这个函数包含了对所有选项的支持，即使我们没有使用到。理想情况下，编译器足够智能，可以预见到Rust程序从未使用这些格式化选项中的任意一个，并把这些部分完全优化掉。</p>
<p>不过，由于<code>fmt::Arguments</code>，那件事情做起来可不简单：它内部有数层跨越了<code>&amp;dyn Write</code>、<code>Argument</code>和函数指针的间接关系，有效地优化这一整根链条不是编译器能做到的。</p>
<p>这意味着<code>write!(x, &quot;{}&quot;, some_str)</code>，这个可以被优化为一个<code>x.write_str(some_str)</code>的调用，还是会导致一个完整的<code>&lt;str as Display&gt;::fmt</code>实现被引入到代码中。这个实现引入了填充和对齐的支持，进而引入了对UTF-8码点和编码UTF-8的支持。真是一堆根本用不到的代码啊！</p>
<p>这对嵌入式项目来说是一个大问题，许多嵌入式Rust开发者因此完全不使用格式化。</p>
<h3 id="运行时开销"><a class="header" href="#运行时开销">运行时开销</a></h3>
<p>即使你拥有了不在乎代码大小和静态存储大小的余裕，你可能还是会关心运行时的性能。</p>
<p>正如之前提到过的，<code>fmt::Arguments</code>结构体被设计为将尽可能多地数据放入静态存储中，以使得在运行时构建<code>fmt::Arguments</code>的成本尽可能低。如今，为了构建这样的一个对象，你必须构建包含参数指针和函数指针的<code>args</code>数组，紧接着是携带指向静态数据和<code>args</code>数组的引用的<code>fmt::Arguments</code>本身（其中的静态数据包括了字符串片段和占位符信息）。</p>
<p>除了指向参数的指针和<code>args</code>数组自己的地址可能会在运行时变化外，其它的一切都永远不会发生改变。举个例子，即使这些数组的长度是常数，它们仍然不得不在运行时作为三个<code>&amp;[]</code>字段的组成部分被写入<code>fmt::Arguments</code>。在此之上，<code>args</code>数组内一半的数据都是常数：指向参数的指针可能会改变，但函数指针是不会改变的。</p>
<p>所以，作为例子，构建<code>format_args!(&quot;{a}{b}{c}&quot;)</code>目前意味着每次执行这条表达式时，需要初始化一个包含指向<code>a</code>,<code>b</code>,<code>c</code>及其格式化函数的指针的<code>args</code>数组和初始化包含三个宽指针（指针+长度）的<code>fmt::Arguments</code>，共计12字（指针或<code>usize</code>）的数据将被写入内存。</p>
<p>在理想情况下，<code>fmt::Arguments</code>可以只有两个指针大：一个指针指向所有的静态数据（字符串片段、占位符和函数指针），另一个指针指向仅包含参数指针的<code>args</code>数组。对我们的例子来说，仅仅有4个指针需要被写入。省下了75%！</p>
<h2 id="一些想法"><a class="header" href="#一些想法">一些想法</a></h2>
<p>所以，我们可以怎样改进呢？</p>
<p>让我们从一些想法开始吧。</p>
<h3 id="闭包"><a class="header" href="#闭包">闭包</a></h3>
<p>观察<code>fmt::Arguments</code>对象的一种方式，是简单地将其看做一个“指令组成的列表”。举个例子，<code>format_args!(&quot;Hello {}\n{:#}!)</code>可以看作：<em>写<code>&quot;Hello&quot;</code></em>，<em>以默认标志显示第一个参数</em>，<em>写一个新行</em>，<em>以美化输出的标志显示第二个参数</em>，<em>写<code>&quot;!&quot;</code></em>，<em>结束</em>。</p>
<p>在Rust中表示指令列表最明显的方式是什么？一系列命令或语句？没错，就是函数，或者闭包。</p>
<p>所以，如果我们把<code>format_args!(&quot;Hello {}\n{:#}!&quot;)</code>展开成闭包会得到什么？</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>fmt::Arguments::new(|w| {
    w.write_str(&quot;Hello &quot;)?;
    Display::fmt(&amp;arg1, Formatter::new(w))?;
    w.write_str(&quot;\n&quot;)?;
    Display::fmt(&amp;arg2, Formatter::new(w).alternate(true))?;
    w.write_str(&quot;!&quot;)?;
    Ok(())
})
<span class="boring">}
</span></code></pre></pre>
<p>如果我们这样做，那么<code>std::fmt::write</code>将会变得很平凡：只要调用闭包就行了！</p>
<p><code>fmt::Arguments</code>将会仅仅包含一个<code>&amp;dyn Fn</code>，在体积上仅仅占两个指针的大小：一个指向函数自己，另一个指向它捕捉到的参数们。完美！</p>
<p>并且最重要的是：编译器现在可以轻易地内联和优化<code>Display::fmt</code>的实现，裁剪掉所有未使用标志的代码！</p>
<p>这听起来几乎好得很不真实。</p>
<p>我<a href="https://link.zhihu.com/?target=https%3A//github.com/rust-lang/rust/pull/101568">实现</a>了它，但不幸的是，虽然它可以<a href="https://link.zhihu.com/?target=https%3A//github.com/rust-lang/rust/pull/101568%23issuecomment-1245327485">显著地优化</a>微型嵌入式程序的二进制体积，但也<a href="https://link.zhihu.com/?target=https%3A//github.com/rust-lang/rust/pull/101568%23issuecomment-1247057102">灾难性地恶化</a>了大型程序的编译时间和二进制大小。</p>
<p>并且这也是有道理的：一个拥有大量print/write/format表达式的程序会突然增加大量需要优化的额外函数体。而内联<code>Display::fmt</code>函数虽然可以减少单个打印语句的开销，但当有很多print表达式时，会导致代码体积剧增。</p>
<h3 id="displaysimple_fmt"><a class="header" href="#displaysimple_fmt">Display::simple_fmt</a></h3>
<p>如果我们想避免引入不必要的格式化（对齐、填充等等）代码，而不想把一切都<code>#[inline]</code>的话，我们就需要采取更精确的方法。</p>
<p>在<code>fmt</code>方法旁，显示特质可以拥有一个额外的方法——让我们称它为<code>simple_fmt</code>——它会做同样的事情，但假定采用的是默认的格式化参数。举个例子，当<code>&lt;&amp;str as Display&gt;::fmt</code>需要支持对齐和填充（并因此需要支持UTF-8解码和记数）时，<code>&lt;&amp;str as Display&gt;::simple_fmt</code>可以被实现为仅仅一行：<code>f.write_str(s)</code>。</p>
<p>然后我们可以更新<code>format_args!()</code>来在没有设置标记的时候使用<code>simple_fmt</code>，而不是<code>fmt</code>，以避免引入不必要的代码。</p>
<p>我也<a href="https://link.zhihu.com/?target=https%3A//github.com/rust-lang/rust/pull/104525">实现</a>了这个想法，并且它工作得很棒：它将一个6KiB的基测程序成功<a href="https://link.zhihu.com/?target=https%3A//github.com/rust-lang/rust/pull/104525%23issuecomment-1318399783">降低</a>到了不到3KiB！</p>
<p><a href="https://link.zhihu.com/?target=https%3A//github.com/rust-lang/rust/pull/104525%23issuecomment-1521546468">不幸的是</a>，如果你的程序使用在某处使用了<code>&amp;dyn Display</code>，这个变化会导致事情稍稍变糟：显示特性的虚表（vtable）将多出一个容纳<code>simple_fmt</code>的条目。</p>
<p>有一些方法可以避免这种情况，但这些方法也有其他的复杂性和局限性。</p>
<h3 id="合并片段和占位符"><a class="header" href="#合并片段和占位符">合并片段和占位符</a></h3>
<p>现在的<code>fmt::Arguments</code>结构体包含三个字段：字符串片段、占位符描述（含有标志等）和参数。前两个字段总是常量、静态的。我们可以把它们合并吗？</p>
<p>如果<code>fmt::Arguments</code>看起来像这样呢？</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct Arguments&lt;'a&gt; {
    template: &amp;'a [Piece&lt;'a&gt;],
    argument: &amp;'a [Argument&lt;'a&gt;],
}

enum Piece&lt;'a&gt; {
    String(&amp;'static str),
    Placeholder {
        argument: usize,
        options: FormattingOptions,
    },
}
<span class="boring">}
</span></code></pre></pre>
<p>那么，<code>format_args!(&quot;&gt; {a}{b} {c}!&quot;)</code>就会扩展成这样的代码：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>Arguments {
    template: &amp;[
        Piece::String(&quot;&gt; &quot;),
        Piece::Placeholder { argument: 0, options: FormattingOptions::default() },
        Piece::Placeholder { argument: 1, options: FormattingOptions::default() },
        Piece::String(&quot; &quot;),
        Piece::Placeholder { argument: 2, options: FormattingOptions::default() },
        Piece::String(&quot;!&quot;),
    ],
    arguments: &amp;[
        Argument::new(&amp;a, Display::fmt),
        Argument::new(&amp;b, Display::fmt),
        Argument::new(&amp;c, Display::fmt),
    ],
}
<span class="boring">}
</span></code></pre></pre>
<p>这将<code>fmt::Arguments</code>的大小从3个宽指针减小到了2个（从6个字降到了4个），并避免了在相邻占位符之间出现空的字符串。</p>
<p>作为<code>placeholders::None</code>优化的替代，对于所有参数都按默认选项顺序格式化的情况（如上面的例子），我们可以增加一条规则，当两个<code>Piece::String</code>元素连续出现时，就代表着它们之间有一个隐式的占位符，因为如果不是这样的话它俩就没有必要分成两部分。</p>
<p>在这条规则下，<code>format_args!(&quot;&gt; {a}{b} {c}!&quot;)</code>就会扩展成这样的代码：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>Arguments {
    template: &amp;[
        Piece::String(&quot;&gt; &quot;),
        Piece::String(&quot;&quot;), // Implicit placeholder for argument 0 above.
        Piece::String(&quot; &quot;), // Implicit placeholder for argument 1 above.
        Piece::String(&quot;!&quot;), // Implicit placeholder for argument 2 above.
    ],
    args: &amp;[
        Argument::new(&amp;a, Display::fmt),
        Argument::new(&amp;b, Display::fmt),
        Argument::new(&amp;c, Display::fmt),
    ],
}
<span class="boring">}
</span></code></pre></pre>
<p>第一眼望去<em>看起来</em>它和优化前的扩展结果效率相似（具有字段<code>pieces:&amp;[&quot;&gt; &quot;, &quot;&quot;, &quot; &quot;, &quot;!&quot;]</code>），但实际消耗了更多的空间。每个<code>Piece</code>元素都远远比单纯的<code>&amp;str</code>更大，因为枚举需要空间来容纳一个也包括了所有格式化选项的<code>Piece::Placeholder</code>。</p>
<p>所以，即使这样做可能会在一定程度上降低运行时的开销（通过减小<code>fmt::Arguments</code>本身的体积），它可能也会导致静态数据增大，并最终造成更大的二进制体积。</p>
<h3 id="指令列表"><a class="header" href="#指令列表">指令列表</a></h3>
<p>在之前的想法上迭代：我们<em>不必</em>让一个字符串片段占用和一个包含了所有可能标志的占位符一样多的空间。</p>
<p>我们可以将占位符分散到多个条目中，从而减小枚举的大小。</p>
<p>举例来说，与其这样：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>[
    Piece::String(&quot;&gt; &quot;),
    Piece::Placeholder {
        argument: 0,
        options: FormattingOptions { alternate: true, … }
    },
]
<span class="boring">}
</span></code></pre></pre>
<p>我们可以这样写：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>[
    Piece::String(&quot;&gt; &quot;),
    Piece::SetAlternateFlag,
    Piece::Placeholder { argument: 0 },
]
<span class="boring">}
</span></code></pre></pre>
<p>现在，设置了标记的占位符将会占用更多的条目，但具有默认设置的占位符并不会为存储所有标记付出代价。</p>
<p>我们在这里创建的实际上是一种格式化的小型“汇编语言”，只有几条指令：写入字符串、设置标志、调用参数的格式化函数。</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>Arguments {
    instructions: &amp;[
        // A list of instructions in our imaginary 'formatting assembly language':
        Instruction::WriteString(&quot;&gt; &quot;),
        Instruction::DisplayArg(0),
        Instruction::WriteString(&quot; &quot;),
        Instruction::SetAlternateFlag,
        Instruction::SetSign(Sign::Plus),
        Instruction::DisplayArg(1),
    ],
    args: &amp;[
        Argument::new(&amp;a, Display::fmt),
        Argument::new(&amp;b, Display::fmt),
        Argument::new(&amp;c, Display::fmt),
    ],
}
<span class="boring">}
</span></code></pre></pre>
<p>如果我们稍微<a href="https://link.zhihu.com/?target=https%3A//github.com/m-ou-se/rust/blob/e89626fcf8d7868cba5fa5cf7d93fa602b58109c/library/core/src/fmt/mod.rs%23L484-L494">在这条路上走远一点</a>，我们甚至可以为我们的格式化命令设计出一种更高效的“指令编码”，并由此引向许多有趣的设计决策和权衡。</p>
<h3 id="静态函数指针"><a class="header" href="#静态函数指针">静态函数指针</a></h3>
<p>作为我们今天的最后一个技巧，让我们看看我们是否能减小<code>args</code>数组的体积。正如上面所提到过的，它既储存了指向参数本身的指针，又储存了（静态的）<code>Display::fmt</code>函数指针。</p>
<p>理想情况下，我们可以将函数指针从<code>args</code>移到<code>instructions</code>来降低运行时开销。（也许可以加入<code>Instruction::DisplayArg</code>中。）</p>
<p>所以，与其这样：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>Arguments {
    instructions: &amp;[
        Instruction::DisplayArg(0),
        Instruction::DisplayArg(1),
        Instruction::DisplayArg(2),
    ],
    args: &amp;[
        Argument::new(&amp;a, Display::fmt),
        Argument::new(&amp;b, Display::fmt),
        Argument::new(&amp;c, Display::fmt),
    ],
}
<span class="boring">}
</span></code></pre></pre>
<p><code>format_args!(&quot;{a}{b}{c}&quot;)</code>会扩展到：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>Arguments {
    instructions: &amp;[
        Instruction::DisplayArg(0, &lt;… as Display&gt;::fmt),
        Instruction::DisplayArg(1, &lt;… as Display&gt;::fmt),
        Instruction::DisplayArg(2, &lt;… as Display&gt;::fmt),
    ],
    args: &amp;[
        Argument::new(&amp;a),
        Argument::new(&amp;b),
        Argument::new(&amp;c),
    ],
}
<span class="boring">}
</span></code></pre></pre>
<p>这会将<code>args</code>的存储体积降低一半！</p>
<p>看起来足够简单，但我们遇到了一个问题：扩展过程不能再依赖于<code>Argument::new</code>的泛型签名来神奇地为每个参数选择正确的<code>Display::fmt</code>，必须为每种参数类型明确指定<code>&lt;T as Display&gt;::fmt</code>。</p>
<p>但是<code>format_args!()</code>只是一个宏，它在扩展时不知道也不可能知道参数的类型，并且Rust目前也没有类似<code>&lt;typeof(a) as Display&gt;::fmt</code>的语法。</p>
<p>处理这个问题是<a href="https://link.zhihu.com/?target=https%3A//github.com/rust-lang/rust/issues/44343">有可能的</a>，但<a href="https://link.zhihu.com/?target=https%3A//github.com/rust-lang/rust/issues/99012%23issuecomment-1177580114">令人意外地tricky！</a></p>
<h2 id="卡住了"><a class="header" href="#卡住了">卡住了？</a></h2>
<p>正如目前已经清晰的，有许多种可能的想法来进行优化。它们中的一些组合得很好，但是许多想法是互斥的。我们也跳过了一些 <a href="https://link.zhihu.com/?target=https%3A//doc.rust-lang.org/stable/std/fmt/struct.Arguments.html%23method.as_str">棘手的细节</a>，比如对<code>Arguments::as_str()</code>的支持。</p>
<p>和一切设计方面的问题一样，任何可能的改变都是有利有弊的。正如我们看到的，一个在二进制大小或运行时性能上表现很棒的实现在编译期表现也会很灾难，举例来说。</p>
<p>但设计问题并不是唯一的问题。</p>
<p>真正使得<code>fmt::Arguments</code>极其难以优化的是改变其实现所需要的<a href="https://link.zhihu.com/?target=https%3A//doc.rust-lang.org/stable/std/fmt/struct.Arguments.html%23method.as_str">精力</a>。你不仅需要改变<code>fmt::Arguments</code>类型，还需要重写内建的<code>format_args!()</code>宏（这涉及到改变<code>rustc</code>本身），并更新<code>fmt::write</code>的实现。并且由于Rust引导的方式，标准库被同时以上个版本和当前版本的编译器编译，所以你需要确保你对标准库做的改动仍然兼容上一版编译器中未修改的<code>format_args!()</code>宏，这会导致[#<a href="https://link.zhihu.com/?target=https%3A//jyn.dev/2023/01/12/Bootstrapping-Rust-in-2023.html">cfg(bootstrap)]困境</a>。更改内建的<code>format_args</code>宏是（或着曾是）<a href="https://link.zhihu.com/?target=https%3A//github.com/rust-lang/rust/blob/1.63.0/compiler/rustc_builtin_macros/src/format.rs">非常有挑战性的</a>，因为它不仅负责生成<code>fmt::Arguments</code>表达式，还负责生成因格式字符串无效而产生的诊断结果。在你克服了上面的所有的困难之后，你会发现Clipy随着你的更改中断了；它依赖于<code>fmt::Arguments</code>的实现细节，因为它是在宏展开<em>之后</em>才进行代码检查的。</p>
<p>所以，即使是对<code>fmt::Arguments</code>的微小改动，也不仅涉及修改标准库，还要求你精通Rustc内建宏、Rustc诊断、引导和Clippy的内部细节。你的改动将同时触及许多不同的部分，这些部分分布在两个代码仓库中，并且需要得到几位不同审阅者的批准。</p>
<p>这正是让事情永远卡住的秘诀。</p>
<h2 id="小步骤"><a class="header" href="#小步骤">小步骤</a></h2>
<p>让事情变得不那么“卡住”的方法，就是<a href="https://link.zhihu.com/?target=https%3A//www.youtube.com/watch%3Fv%3DDnYQKWs_7EA">把这件事拆分成很多小步骤，把阻碍一一来解决</a>。这可能会让人有些疲惫，因为回报（比如实际性能的提高）不会很快到来。但是如果你喜欢冗长的todo清单（我就是这样的人），你就会乐在其中了:)。</p>
<p>正如我在<a href="https://link.zhihu.com/?target=https%3A//github.com/rust-lang/rust/issues/93740">std中的锁的工作中</a>做的那样，我创建了一个issue来跟踪所有和优化<code>fmt::Arguments</code>相关的事情：<a href="https://link.zhihu.com/?target=https%3A//github.com/rust-lang/rust/issues/99012">https://github.com/rust-lang/rust/issues/99012</a></p>
<p>正如你可以在那里的todo清单上看到的，尽管（目前！）还几乎没有改变<code>fmt::Arguments</code>工作方式的更改，但其它更改已经有很多了。迄今为止所做的更改，在一定程度上通过偿还了技术层面上欠下的债，为日后的改进提供了极大的便利。</p>
<p>举个例子，在<a href="https://link.zhihu.com/?target=https%3A//github.com/rust-lang/rust/pull/100996">其中一项更改中</a>，我重构了内建宏<code>format_args</code>来将解析（parseing）、解读（resolving）、生成诊断信息、展开步骤分开（就像一个迷你编译器），这样可以让别人在不了解其它步骤的情况下也能修改展开步骤。</p>
<p>然后，我<a href="https://link.zhihu.com/?target=https%3A//github.com/rust-lang/compiler-team/issues/541">提议</a>让<code>format_args</code>宏的展开变得更加神奇，实际上是将展开步骤推迟到稍后再进行，这解锁了<a href="https://link.zhihu.com/?target=https%3A//github.com/rust-lang/rust/issues/78356">一些非常酷的优化</a>（已经<a href="https://link.zhihu.com/?target=https%3A//github.com/rust-lang/rust/pull/109999s%3A//github.com/rust-lang/rust/pull/109999">作为Rust 1.71的一部分发布</a>，同时也允许Clippy访问未展开的信息，这样它最终可以不再依赖于宏的实现细节，使得当展开方式更改的时候它不会因此停摆。</p>
<p>由于其它活动部件（如Clippy）所依赖的细节（大部分）保持不变，因此<a href="https://link.zhihu.com/?target=https%3A//github.com/rust-lang/rust/pull/106745">这一更改</a>在很大程度上又是自成一体的，而且可以由一个人进行review。在它被合并之后，下一步就是将Clippy由使用实现细节迁移到使用新可用的信息，这在另一个<a href="https://link.zhihu.com/?target=https%3A//github.com/rust-lang/rust-clippy/issues/10233">issue</a>中跟踪。</p>
<p>而在所有这些工作都完成后，<code>fmt::Arguments</code>甚至<em>还</em>没有被更改过！但我们现在已经把<a href="https://link.zhihu.com/?target=https%3A//sketchplanations.com/yak-shaving">牦牛大部分的毛剃光了</a>，<em>终于</em>不用再钻另一个兔子洞就可以进行改进了。</p>
<blockquote>
<p>译注：给牦牛剃毛（Yak shaving）是一句习语，形容的是当你要开始做一件事情时，发现自己为了做这件事必须先做另一件事，如此循环，直到你最终发现自己在做给牦牛剃毛这种和最初的目标完全无关的事情。</p>
</blockquote>
<h2 id="接下来做什么"><a class="header" href="#接下来做什么">接下来做什么？</a></h2>
<p>由于大改进的更改还在不断慢慢推进（并且我也有<a href="https://link.zhihu.com/?target=https%3A//blog.m-ou.se/rust-standard/">别的事</a>要忙），因此大部令人激动的结果仍然在到来的路上。但是，已经有一些有趣的事情可以让我们感到激动了：</p>
<ul>
<li>
<p>在Rust 1.71中，<a href="https://link.zhihu.com/?target=https%3A//github.com/rust-lang/rust/pull/106824">嵌套的format_args调用会被扁平化了</a>。</p>
<p>举个例子，<code>format_args!(&quot;[{}] {}&quot;, 123, format_args!(&quot;error: {}&quot;, msg))</code>现在实际上等于<code>format_args!(&quot;[123] error: {}&quot;, msg)</code>。这意味着像<code>dbg!()</code>这样的宏现在会有小得多的开销。</p>
<p>这会导致很大的提升，甚至对大型程序也如此。举例来说，<code>hyper</code>在<a href="https://link.zhihu.com/?target=https%3A//perf.rust-lang.org/compare.html%3Fstart%3D0b90256ada21c6a81b4c18f2c7a23151ab5fc232%26end%3Db19488dba41be7ad94ed1d2933dd71ff8a300b37%26stat%3Dinstructions%3Au">编译时间</a>和<a href="https://link.zhihu.com/?target=https%3A//perf.rust-lang.org/compare.html%3Fstart%3D0b90256ada21c6a81b4c18f2c7a23151ab5fc232%26end%3Db19488dba41be7ad94ed1d2933dd71ff8a300b37%26stat%3Dsize%3Alinked_artifact">二进制体积</a>上都提升了大约2%~3%。</p>
</li>
<li>
<p>对无标志的占位符的优化将会删除没有用到的格式化代码，这会为小型程序带来<a href="https://link.zhihu.com/?target=https%3A//github.com/rust-lang/rust/pull/104525%23issuecomment-1318399783">超过50%的二进制体积降低</a>。</p>
</li>
<li>
<p><a href="https://link.zhihu.com/?target=https%3A//github.com/rust-lang/rust/pull/115129">优化过后的fmt::Arguments表示</a>将不仅降低运行时的开销，而且也继续兼容<code>fmt::Arguments::from_str()</code>方法。</p>
</li>
</ul>
<p>但这与我们最终通过实施一些更大的想法而在未来获得的潜在改进相比，简直都不值一提。过去，实现这些想法异常困难，但现在情况已经有所改变，并且还在越变越好：</p>
<ul>
<li>最重要的是，得益于重构，修改<code>format_args!()</code>和<code>fmt::Arguments</code>不再需要同步修改rustc的诊断信息或Clippy。尽管还需要修改一个rustc的内建宏，但现在做起来也简单很多了。</li>
<li>当前，标准库需要同时用旧版和新版的rustc编译，给修改<code>fmt::Arguments</code>造成了很多麻烦（带有大量的<code>#[cfg(bootstrap)]</code>），但<a href="https://link.zhihu.com/?target=https%3A//github.com/rust-lang/compiler-team/issues/619">改变Rustc引导中的stage 0的计划</a>将会，<a href="https://link.zhihu.com/?target=https%3A//rust-lang.zulipchat.com/%23narrow/stream/233931-t-compiler.2Fmajor-changes/topic/Redesign.20bootstrap.20stages.20compiler-team.23619">一旦有人着手实现了</a>，完全地解决掉这个问题，让修改内建宏变得简单很多很多。</li>
<li>Rust编译器的<a href="https://link.zhihu.com/?target=https%3A//perf.rust-lang.org/">性能追踪器</a>现在也追踪二进制体积的大小了，这使得来自修改<code>fmt::Arguments</code>的优化可以比以前更轻松地被追踪。</li>
<li>现在有一个正视的<a href="https://link.zhihu.com/?target=https%3A//www.rust-lang.org/governance/teams/compiler%23Binary%20size%20working%20group">二进制体积工作组</a>，对缩小格式化代码非常感兴趣。</li>
</ul>
<p>换句话说：我们可以期待未来会有许多令人兴奋的改进，而我们正在一步一步地实现它们。</p>
<blockquote>
<p>译者后记</p>
<p>这篇文章翻译的体验很好，原文句式清晰简洁，没有复杂的从句和词藻，你也可以去读读看。</p>
<p>Rust的fmt包是一个非常有意思的大宝库，纵使你对Rust有再多怨言，你也不应该否认std::fmt的设计非常地精巧，通过和Write宏的配合，使得开发者不论是使用Rust的format!等宏，还是在为自己的库适配format_args!，都能得到很舒适的体验。用我mentor的话来说，就是Rust的API帮我们抹平了很多fmt相关的细节。</p>
<p>作者对<code>core::fmt</code>及其局限的了解非常透彻，这也和他活跃开发者的身份相关。在嵌入式开发中，人们确实会在意他所提到的<code>fmt</code>的那些问题，并且社区中也出现了<code>ufmt</code>这样针对二进制大小和编译时间优化的、无动态派发的<code>fmt</code>替代。不过，作者也清晰地了解每一种解决方案的利弊，并且作为标准库这种牵一发而动全身的重要部位，作者也表现出了足够的谨慎和全局思维。</p>
<p>文章中作者花大量笔墨介绍了fmt::Arguments的实现细节及改进空间。对感兴趣的人来说是不可多得的财宝，因为你很难找到第二个对Rust的格式化如此熟悉和经验丰富的专家了，更何况他还为我们写下了这篇内容丰富的文章。</p>
</blockquote>
<p>发布于 2024-04-20 23:43・IP 属地江苏</p>

                    </main>

                    <nav class="nav-wrapper" aria-label="Page navigation">
                        <!-- Mobile navigation buttons -->
                            <a rel="prev" href="../../08_奇技淫巧/“变长参数”函数与回调/“变长参数”函数与回调.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
                                <i class="fa fa-angle-left"></i>
                            </a>

                            <a rel="next" href="../../08_奇技淫巧/给Rust带来一点C++特产/给Rust带来一点C++特产.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
                                <i class="fa fa-angle-right"></i>
                            </a>

                        <div style="clear: both"></div>
                    </nav>
                </div>
            </div>

            <nav class="nav-wide-wrapper" aria-label="Page navigation">
                    <a rel="prev" href="../../08_奇技淫巧/“变长参数”函数与回调/“变长参数”函数与回调.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
                        <i class="fa fa-angle-left"></i>
                    </a>

                    <a rel="next" href="../../08_奇技淫巧/给Rust带来一点C++特产/给Rust带来一点C++特产.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
                        <i class="fa fa-angle-right"></i>
                    </a>
            </nav>

        </div>




        <script type="text/javascript">
            window.playground_copyable = true;
        </script>


        <script src="../../elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="../../mark.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="../../searcher.js" type="text/javascript" charset="utf-8"></script>

        <script src="../../clipboard.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="../../highlight.js" type="text/javascript" charset="utf-8"></script>
        <script src="../../book.js" type="text/javascript" charset="utf-8"></script>

        <!-- Custom JS scripts -->


    </body>
</html>
